package cn.hicard.risk.action.am;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.nio.charset.Charset;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.log4j.Logger;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

import cn.hicard.risk.common.Constant;
import cn.hicard.risk.io.Compress;
import cn.hicard.risk.io.Fileinfo;
import cn.hicard.util.AppConfig;
import cn.hicard.risk.vo.antimoney.PSTR;

/**
 * 反洗钱处理相关供FLEX调用的类
 * @author zhangy
 *
 */
public class AntimoneyFlex {
		
	/** 正常(N)报告(PS)类型，其标识NPS*/
	public static final String NORMAL_TYPE = "NPS";
	/** 正常(N)补充(PC)类型，其标识NPC*/
	public static final String NORMAL_EXTRA_TYPE = "NPC";
	/** 数据包文件扩展名 .ZIP*/
	public static final String PACKAGE_ZIP = ".ZIP";
	/** 报文文件扩展名 .XML*/
	public static final String DATAGRAM_XML = ".XML";
	/** 连接符 */
	public static final String JOINER = "-";
	/** 对报告个例不适用的数据项填写“@N”；*/
	public static final String NOT_APPLY = "@N";
	/** 因报告机构自身业务系统原因暂时无法得到的数据项填写“@I”；*/
	public static final String NOT_SUPPORT = "@I";
	/** 因支付结算系统等国家金融基础设施及国际支付清算体系原因无法得到的数据项用“@E”来表示。*/
	public static final String NOT_GET = "@E";
	/** 数据包大小 */
	public static final long PACKAGE_SIZE = 1024 * 100;
	// 汇卡的机构编号，14位
	public static final String ORG_NO = "Z2019644000011";	
	
	// 报文存放的相对目录	确保路径以'/'结尾
	private String datagramPath = "";
	// 数据包批次号
	private String packBatchNo = "";
	// 报文文件批次号
	private String xmlBatchNo = "";	
	
	/** 日志记录工具 */
	private static Logger logger = Logger.getLogger(Constant.LOGGER_MAIN);
	
	/**
	 * 构造初始化
	 */
	public AntimoneyFlex(){
		
		// 报文存放的相对目录	确保路径以'/'结尾
		this.datagramPath = AppConfig.get("antimoney_datagram");
		if(datagramPath.charAt(datagramPath.length()-1) != '/'){
			datagramPath += '/';
		}
		// 转为绝对路径 : classes -> WEB-INF -> /
		datagramPath = getClass().getResource("/../../").getPath() + datagramPath;
		datagramPath = datagramPath.replace("//", "/");
	}
	
	/**
	 * 查找当前的文件，组装为XML返回给FLEX显示用
	 * @param date
	 * @param type
	 * @return
	 */
	public String findFiles(final String date, final String type){
		String[] files = diskFiles(date, type);
		logger.debug("在日期[" + date + "]内扫描到文件数量[" + files.length + "]");
		Element root = DocumentHelper.createElement("files");
		for (int i=0; i<files.length; i++) {
			String file = files[i];
			Element elt = root.addElement("file");
			elt.addAttribute("sequence", String.valueOf(i+1));
			elt.addAttribute("date", date);
			elt.addAttribute("type", file.substring(file.indexOf('.')));
			elt.addAttribute("filename", file);
			elt.addAttribute("size", Fileinfo.sizeInfo(new File(datagramPath + file)));
		}
		String xml = root.asXML();
		logger.debug("生成FLEX数据源XML内容：\r\n" + xml);
		return xml;
	}

	
	/**
	 * 将可疑交易数据对象保存为XML报文文件
	 * 防止重复，使用线程同步，处理FLEX请求时，本类只会被创建一次，所以使用自身锁定
	 * @param pstr
	 */
	public synchronized String saveXmlDatagram(String date, PSTR pstr) { 
		
		String xml = pstr.toXml();			
		logger.debug("从页面传送过来的可以交易报文XML内容为：\r\n" + xml);
		
		// 组装报文XML文件名
		String xmlFilename = nextXmlDatagramName(date);	
		
		// 保存XML报文件
		FileOutputStream fos = null;
		try {
			fos = new FileOutputStream(datagramPath + xmlFilename);
			fos.write(xml.getBytes(Charset.forName("gb18030")));
			fos.close();
		} catch (Exception e) {
			logger.error("", e);
			return "发生错误：" + e.getMessage();
		}
		return "成功保存：\r\n" + xmlFilename;
	}
	
	/**
	 * 扫描并打包XML报文文件成为ZIP <br/>
	 * 防止重复，使用线程同步，处理FLEX请求时，本类只会被创建一次，所以使用自身锁定
	 * @param date 要扫描的报送日期
	 */
	public synchronized String scanAndPackToZip(String date){

		// NPSXXXXXXXXXXXXXX-20100306-0002.ZIP
		String nextBatchNo = "0001";		
		String[] zipFiles = diskFiles(date, PACKAGE_ZIP);					
		if(zipFiles.length != 0){
			logger.debug("已存在的所有ZIP数据包文件数量：" + zipFiles.length);	
			// 文件名从小到大排序
			Arrays.sort(zipFiles);	
			// 文件名示例 NPS1234567890abcd-20130505-0001.ZIP
			// 取得最大的批次号
			String maxBatchNo = zipFiles[zipFiles.length-1].substring(27, 31);
			// 下一个批次号
			DecimalFormat df = new DecimalFormat("0000");
			nextBatchNo = df.format(Integer.valueOf(maxBatchNo) + 1);
		}
		// 查找批次号内的XML
		String[] xmlfiles = diskFiles(date, DATAGRAM_XML, nextBatchNo);
		if(xmlfiles.length == 0){
			return "暂未发现日期[" + date + "]内批号为[" + nextBatchNo + "]的XML报文文件";
		}
		
		// 压缩包名称
		StringBuilder zipfile = new StringBuilder();
		zipfile.append(NORMAL_TYPE);
		zipfile.append(ORG_NO);
		zipfile.append(JOINER);
		zipfile.append(date);
		zipfile.append(JOINER);
		zipfile.append(nextBatchNo);
		zipfile.append(PACKAGE_ZIP);
		// 压缩
		List<String> files = new ArrayList<String>();
		for (String xml : xmlfiles) {
			files.add(datagramPath + xml);
		}		
		try {
			Compress.zip(files, datagramPath + zipfile);
		} catch (Exception e) {
			logger.error("", e);
			return "压缩文件时发生错误：" + e.getMessage();
		}
		
		return "文件压缩完成：\r\n" + zipfile;	
	}
	
	/**
	 * 删除文件
	 * @param filename
	 */
	public synchronized void deleteFile(String filename){
		String filepath = datagramPath + filename;
		// 文件还是先留起来再说
		String target = datagramPath + "delete-" + System.currentTimeMillis() + "-" + filename;
		Fileinfo.move(filepath, target);
	}
	
	/**
	 * 生成报文文件名<BR/>
	 * 报文类型和交易报告类型标识＋报告机构编码＋报送日期＋当日报送批次＋文件在该批次中的编号.XML
	 * @param date 报送日期，格式yyyyMMdd
	 * @return
	 */
	private String nextXmlDatagramName(String date){
		
		// 取得所有XML
		String[] xmlFiles = diskFiles(date, DATAGRAM_XML);			
		logger.debug("已存在的所有XML报文文件数量：" + xmlFiles.length);
		if(xmlFiles.length == 0){
			packBatchNo = "0001";
			xmlBatchNo = "0001";
		} else {
			// 文件名从小到大排序
			Arrays.sort(xmlFiles);	
			// 文件名示例 NPS1234567890abcd-20130505-0001-0001.XML
			// 取得最大的批次号
			String maxBatchNo = xmlFiles[xmlFiles.length-1].substring(27, 31);
			// 取得最大序号
			String maxXmlBatchNo = xmlFiles[xmlFiles.length-1].substring(32, 36);
			// 该批次号中的文件
			xmlFiles = diskFiles(date, DATAGRAM_XML, maxBatchNo);
			// 统计批次号的XML文件大小，不超过5MB一个批次号，稍微大一点也没关系，压缩后就小了（实际上应该计算压缩包大小，但是太麻烦了）
			DecimalFormat df = new DecimalFormat("0000");
			if(sumSize(xmlFiles) > PACKAGE_SIZE){
				// 超过指定大小，则批号+1，序号初始值
				packBatchNo = df.format(Integer.valueOf(maxBatchNo) + 1);
				xmlBatchNo = "0001";				
			}else{
				// 没超大小，批号不变，序号+1
				packBatchNo = maxBatchNo;
				xmlBatchNo = df.format(Integer.valueOf(maxXmlBatchNo) + 1);
			}
		}				
		StringBuilder sb = new StringBuilder();
		sb.append(NORMAL_TYPE);
		sb.append(ORG_NO);
		sb.append(JOINER);
		sb.append(date);
		sb.append(JOINER);
		sb.append(packBatchNo);
		sb.append(JOINER);
		sb.append(xmlBatchNo);
		sb.append(DATAGRAM_XML);
		return sb.toString();
	}
	
	/**
	 * 取得硬盘上已存的报文XML和ZIP文件
	 * @param date 文件的报送日期，从文件名中截取，格式yyyyMMdd
	 * @param type 文件类型是.XML还是.ZIP还是全部*
	 * @return
	 */
	private String[] diskFiles(final String date, final String type){
		return diskFiles(date, type, null);
	}
	
	/**
	 * 取得硬盘上已存的报文XML和ZIP文件
	 * @param date 文件的报送日期，从文件名中截取，格式yyyyMMdd
	 * @param type 文件类型是.XML还是.ZIP还是全部*
	 * @param batchno 批次号，即ZIP的序号和XML的包序号
	 * @return
	 */
	private String[] diskFiles(final String date, final String type, final String batchno){
		File dir = new File(datagramPath);
		String[] files = dir.list(new FilenameFilter() {			
			@Override
			public boolean accept(File dir, String name) {
				// 指定扩展名 // *表示全部的XML与ZIP
				if("*".equals(type)){
					if( ! name.endsWith(PACKAGE_ZIP) && ! name.endsWith(DATAGRAM_XML)) return false;
				}else{
					if( ! name.endsWith(type)) return false;
				}			
				// 指定文件名 
				// NPS1234567890abcd-20130505-0001.ZIP
				// 数据包长度3+14+8+4+4    =33 + 2 = 35
				// NPS1234567890abcd-20130505-0001-0001.XML
				// 报文长度　3+14+8+4+4+4=37 + 3 = 40
				if(name.length() != 40 && name.length() != 35) return false;
				// 连接符坐标
				int index = name.indexOf(JOINER);
				// 连接符不正确
				if(index==-1) return false;
				// 截取当天的日期
				if( ! date.equals(name.substring(index+1, index+9))) return false;
				// 截取批次号
				if(batchno != null){
					if( ! name.substring(27, 31).equals(batchno)) return false;					
				}
				
				// 其它的不管了，其实写个正则表达式一次就OK，但是懒得查
				
				return true;
			}
		});
		return files;
	}
	
	/**
	 * 统计文件大小
	 * @param files
	 * @return
	 */
	private long sumSize(String[] files){
		long size = 0;
		for (String path : files) {
			size += new File(datagramPath + path).length();
		}
		return size;
	}
	
}
